use core::str;
use std::{path::Path, sync::{Arc, Mutex}};

use git2::{DiffOptions, Signature};
use lazy_static::lazy_static;

use crate::{error, info};

use super::{config::DATA_PATH, file_content::FileContent};

lazy_static! {
	pub static ref REPO: Arc<Mutex<git2::Repository>> = init_git();
}

pub fn init_git() -> Arc<Mutex<git2::Repository>> {
	let data_path = DATA_PATH.lock().unwrap().clone();
	info!(format!("initializing data path at: {}", data_path));

	match git2::Repository::init(data_path.clone()) {
		Ok(_) => {},
		Err(e) => { error!(e); }, 
	};

	let repo = git2::Repository::open(data_path.clone()).unwrap();
	if repo.head().is_err() {
		info!("in a new data dir. init a repo.");
		let signature = Signature::now("echest", "echest@takemeto.icu").unwrap();
		repo.commit(
			Some("HEAD"), 
			&signature,
			&signature,
			"init",
			&repo.find_tree(repo.index().unwrap().write_tree().unwrap()).unwrap(),
			&[],
		).unwrap();
	}

	Arc::new(Mutex::new(repo))
}

pub fn get_cluster_diff_list(cluster_name: &String, old_version: &String) -> Result<Vec<String>, String> {
	let mut ret = Vec::new();

	let repo = REPO.lock().unwrap();
	let head_commit = repo.head().unwrap().peel_to_commit().unwrap();

	let old_commit = git2::Oid::from_str(&old_version);
	if old_commit.is_err() {
		return Err("Invalid old version".to_string());
	}
	let old_commit = old_commit.unwrap();

	let old_commit = repo.find_commit(old_commit);
	if old_commit.is_err() {
		return Err("Invalid old version".to_string());
	}
	let old_commit = old_commit.unwrap();

	let old_tree = old_commit.tree().unwrap();
	let head_tree = head_commit.tree().unwrap();

	let mut diff_opts = DiffOptions::new();
	let diffs = repo.diff_tree_to_tree(
		Some(&head_tree),
		Some(&old_tree),
		Some(&mut diff_opts)
	);
	if diffs.is_err() {
		return Err("Cannot get diff between these versions".to_string());
	}
	let diffs = diffs.unwrap();

	for d in diffs.deltas() {
		let file_path = d.new_file().path().unwrap().as_os_str().to_str().unwrap().to_string();
		if !file_path.starts_with(cluster_name) {
			continue;
		}
		ret.push(file_path);
	}

	return Ok(ret);
}

pub fn get_version_file_content(version: &String, file_list: &Vec<String>) -> Result<Vec<FileContent>, String> {
	let mut ret = Vec::new();

	let version_commit = git2::Oid::from_str(&version);
	if version_commit.is_err() {
		return Err("invalid version".to_string());
	}
	let version_commit = version_commit.unwrap();

	let repo = REPO.lock().unwrap();
	let version_commit = repo.find_commit(version_commit);
	if version_commit.is_err() {
		return Err("invalid version".to_string());
	}
	let version_commit = version_commit.unwrap();

	let commit_tree = version_commit.tree();
	if commit_tree.is_err() {
		return Err("invalid version".to_string());
	}
	let commit_tree = commit_tree.unwrap();

	for file_path_str in file_list {
		let file_path = Path::new(&file_path_str);
		let entry = commit_tree.get_path(file_path);
		if entry.is_err() { continue; }
		let entry = entry.unwrap();
		let blob = repo.find_blob(entry.id());
		if blob.is_err() { continue; }
		let blob = blob.unwrap();
		let content = str::from_utf8(blob.content());
		if content.is_err() { continue; }
		let content = content.unwrap().to_string();
		ret.push(FileContent{
			name: file_path_str.clone(),
			content,
		});
	}

	Ok(ret)
}

pub fn get_latest_version() -> String {
	REPO.lock().unwrap().head().unwrap().target().unwrap().to_string()
}
